package chapt08.sphereworld;
// SphereWorld.java
// OpenGL SuperBible
// Demonstrates an immersive 3D environment using actors
// and a camera.  This version adds lights and material properties
// and shadows.
// Program by Richard S. Wright Jr.

import java.nio.ByteBuffer;
import java.io.IOException;

import javax.media.opengl.GL2;
import javax.media.opengl.glu.gl2.GLUgl2;

import com.jogamp.opengl.util.gl2.GLUT;
import com.jogamp.opengl.util.texture.spi.TGAImage;

import java.util.Random;

import shared.*;

public class SphereWorld
{
	static GLUgl2 glu = new GLUgl2();
	static GLUT glut = new GLUT();
	
	static int NUM_SPHERES = 30;
	static GLFrame spheres[] = new GLFrame[NUM_SPHERES];
	static GLFrame frameCamera = new GLFrame();
	
	// Light and material data
	static float fLightPos[] = {-100.0f, 100.0f, 50.0f, 1.0f}; // Point source
	static float fNoLight[] = {0.0f, 0.0f, 0.0f, 0.0f};
	static float fLowLight[] = {0.25f, 0.25f, 0.25f, 1.0f};
	static float fBrightLight[] = {1.0f, 1.0f, 1.0f, 1.0f};
	
	static float mShadowMatrix[] = new float[16];
	
	static int GROUND_TEXTURE = 0;
	static int TORUS_TEXTURE = 1;
	static int SPHERE_TEXTURE = 2;
	static int NUM_TEXTURES = 3;
	static int textureObjects[] = new int[NUM_TEXTURES];
	
	static String szTextureFiles[] = {"grass.tga", "wood.tga", "orb.tga"};
	
	static float yRot = 0.0f; // Rotation angle for animation
	
	//////////////////////////////////////////////////////////////////
	// This function does any needed initialization on the rendering
	// context. 
	protected static void setup(GL2 gl2, int width, int height)
	{
		float vPoints[][] = {
			{0.0f, -0.4f, 0.0f},
			{10.0f, -0.4f, 0.0f},
			{5.0f, -0.4f, -5.0f}
		};
		
		int iSphere;
		Random random = new Random();
		
		// Grayish background
		gl2.glClearColor(fLowLight[0], fLowLight[1], fLowLight[2], fLowLight[3]);

	    // Clear stencil buffer with zero, increment by one whenever anybody
	    // draws into it. When stencil function is enabled, only write where
	    // stencil value is zero. This prevents the transparent shadow from drawing
	    // over itself
		gl2.glStencilOp(GL2.GL_INCR, GL2.GL_INCR, GL2.GL_INCR);
		gl2.glClearStencil(0);
		gl2.glStencilFunc(GL2.GL_EQUAL, 0x0, 0x01);
		
		// Cull backs of polygons
		gl2.glCullFace(GL2.GL_BACK);
		gl2.glFrontFace(GL2.GL_CCW);
		//gl2.glEnable(GL2.GL_CULL_FACE);
		gl2.glEnable(GL2.GL_DEPTH_TEST);
		
		// Setup light parameters
		gl2.glLightModelfv(GL2.GL_LIGHT_MODEL_AMBIENT, fNoLight, 0);
		gl2.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, fLowLight, 0);
		gl2.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, fBrightLight, 0);
		gl2.glLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, fBrightLight, 0);
		gl2.glEnable(GL2.GL_LIGHTING);
		gl2.glEnable(GL2.GL_LIGHT0);
		
		// Calculate shadow matrix
		float pPlane[] = new float[4];
		M3D.getPlaneEquation(pPlane, vPoints[0], vPoints[1], vPoints[2]);
		M3D.makePlanarShadowMatrix(mShadowMatrix, pPlane, fLightPos);
		
		// Mostly use material tracking
		gl2.glEnable(GL2.GL_COLOR_MATERIAL);
		gl2.glColorMaterial(GL2.GL_FRONT, GL2.GL_AMBIENT_AND_DIFFUSE);
		gl2.glMaterialfv(GL2.GL_FRONT, GL2.GL_SPECULAR, fBrightLight, 0);
		gl2.glMateriali(GL2.GL_FRONT, GL2.GL_SHININESS, 128);
		
		// Randomly place the sphere inhabitants
		for(iSphere = 0; iSphere < NUM_SPHERES; iSphere++)
		{
			// Pick a random location between -20 and 20 at .1 increments
			float x = ((float)random.nextInt(400) - 200) * 0.1f;
			float z = ((float)random.nextInt(400) - 200) * 0.1f;
			spheres[iSphere] = new GLFrame();
			spheres[iSphere].setOrigin(x, 0.0f, z);
		}
		
		// Set up texture maps
		gl2.glEnable(GL2.GL_TEXTURE_2D);
		gl2.glGenTextures(NUM_TEXTURES, textureObjects, 0);
		gl2.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_MODULATE);
		
		for(int i = 0; i < NUM_TEXTURES; i++)
		{
			ByteBuffer pBytes = null;
			int iWidth, iHeight;
			int eFormat;
			
			gl2.glBindTexture(GL2.GL_TEXTURE_2D, textureObjects[i]);
			
			// Load this texture map
			try
			{
				TGAImage tgaImage = TGAImage.read(SphereWorld.class.getResourceAsStream(szTextureFiles[i]));
				iWidth = tgaImage.getWidth();
				iHeight = tgaImage.getHeight();
				eFormat = tgaImage.getGLFormat();
				pBytes = tgaImage.getData();
				glu.gluBuild2DMipmaps(GL2.GL_TEXTURE_2D, GL2.GL_RGB, iWidth, iHeight, eFormat, GL2.GL_UNSIGNED_BYTE, pBytes);
				pBytes = null;
			}
			catch(IOException e)
			{
				
			}
			
			gl2.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR);
			gl2.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_LINEAR_MIPMAP_LINEAR);
			gl2.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP_TO_EDGE);
			gl2.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP_TO_EDGE);
		}
	}

	////////////////////////////////////////////////////////////////////////
	// Do shutdown for the rendering context
	static void shutdownRC(GL2 gl2)
	{
		gl2.glDeleteTextures(NUM_TEXTURES, textureObjects, 0);
	}
	
	///////////////////////////////////////////////////////////
	// Draw the ground as a series of triangle strips
	static void drawGround(GL2 gl2)
	{
		float fExtent = 20.0f;
		float fStep = 1.0f;
		float y = -0.4f;
		float iStrip, iRun;
		float s = 0.0f;
		float t = 0.0f;
		float texStep = 1.0f / (fExtent * .075f);
		
		gl2.glBindTexture(GL2.GL_TEXTURE_2D, textureObjects[GROUND_TEXTURE]);
		gl2.glTexParameterf(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_REPEAT);
		gl2.glTexParameterf(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_REPEAT);
		
		gl2.glColor3f(1.0f, 1.0f, 1.0f);
		for(iStrip = -fExtent; iStrip <= fExtent; iStrip += fStep)
		{
			t = 0.0f;
			gl2.glBegin(GL2.GL_TRIANGLE_STRIP);
				
				for(iRun = fExtent; iRun >= -fExtent; iRun -= fStep)
				{
					gl2.glTexCoord2f(s, t);
					gl2.glNormal3f(0.0f, 1.0f, 0.0f); // All Point up
					gl2.glVertex3f(iStrip, y, iRun);
					
					gl2.glTexCoord2f(s + texStep, t);
					gl2.glNormal3f(0.0f, 1.0f, 0.0f); // All Point up
					gl2.glVertex3f(iStrip + fStep, y, iRun);
					
					t += texStep;
				}
			gl2.glEnd();
			s += texStep;
		}
	}
	
	///////////////////////////////////////////////////////////////////////
	// Draw random inhabitants and the rotating torus/sphere duo
	static void drawInhabitants(GL2 gl2, boolean nShadow)
	{
		int i;
		
		if(! nShadow)
		{
			yRot += 0.5f;
			gl2.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
		}
		else
			gl2.glColor4f(0.0f, 0.0f, 0.0f, .6f); // Shadow color
		
		// Draw the randomly located spheres
		gl2.glBindTexture(GL2.GL_TEXTURE_2D, textureObjects[SPHERE_TEXTURE]);
		for(i = 0; i < NUM_SPHERES; i++)
		{
			gl2.glPushMatrix();
			spheres[i].applyActorTransform(gl2);
			GLT.drawSphere(gl2, 0.3f, 21, 11);
			gl2.glPopMatrix();
		}
		
		gl2.glPushMatrix();
			gl2.glTranslatef(0.0f, 0.1f, -2.5f);

			gl2.glPushMatrix();
				gl2.glRotatef(-yRot * 2.0f, 0.0f, 1.0f, 0.0f);
				gl2.glTranslatef(1.0f, 0.0f, 0.0f);
				GLT.drawSphere(gl2, 0.1f, 21, 11);
			gl2.glPopMatrix();
			
			if(! nShadow)
			{
				// Torus alone will be specular
				gl2.glMaterialfv(GL2.GL_FRONT, GL2.GL_SPECULAR, fBrightLight, 0);
			}
			
			gl2.glRotatef(yRot, 0.0f, 1.0f, 0.0f);
			gl2.glBindTexture(GL2.GL_TEXTURE_2D, textureObjects[TORUS_TEXTURE]);
			GLT.drawTorus(gl2, 0.35f, 0.15f, 61, 37);
			gl2.glMaterialfv(GL2.GL_FRONT, GL2.GL_SPECULAR, fNoLight, 0);
		gl2.glPopMatrix();
	}
	
	// Called to draw scene
	protected static void render(GL2 gl2, int w, int h)
	{
		float fAspect;
		
		// Prevent a divide by zero, when window is too short
		// (you can't make a window of zero width).
		if(h == 0)
			h = 1;
		
		gl2.glViewport(0, 0, w, h);
		
		fAspect = (float)w / (float)h;
		
		// Reset the coordinate system before modifying
		gl2.glMatrixMode(GL2.GL_PROJECTION);
		gl2.glLoadIdentity();
		
		// Set the clipping volume
		glu.gluPerspective(35.0f, fAspect, 1.0f, 50.0f);
		
		gl2.glMatrixMode(GL2.GL_MODELVIEW);
		gl2.glLoadIdentity();
		
		// Clear the window with current clearing color
		gl2.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT | GL2.GL_STENCIL_BUFFER_BIT);
		
		gl2.glPushMatrix();
			frameCamera.applyCameraTransform(gl2);
			
			// Position light before any other transformations
			gl2.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, fLightPos, 0);
			
			// Draw the ground
			gl2.glColor3f(0.6f, 0.4f, 0.1f);
			drawGround(gl2);
			
			// Draw shadows first
			gl2.glDisable(GL2.GL_DEPTH_TEST);
			gl2.glDisable(GL2.GL_LIGHTING);
			gl2.glDisable(GL2.GL_TEXTURE_2D);
			gl2.glEnable(GL2.GL_BLEND);
			gl2.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA);
			gl2.glEnable(GL2.GL_STENCIL_TEST);
			gl2.glPushMatrix();
				gl2.glMultMatrixf(mShadowMatrix, 0);
				drawInhabitants(gl2, true);
			gl2.glPopMatrix();
			gl2.glDisable(GL2.GL_STENCIL_TEST);
			gl2.glDisable(GL2.GL_BLEND);
			gl2.glEnable(GL2.GL_LIGHTING);
			gl2.glEnable(GL2.GL_TEXTURE_2D);
			gl2.glEnable(GL2.GL_DEPTH_TEST);
			
			// Draw inhabitants normally
			drawInhabitants(gl2, false);

		gl2.glPopMatrix();
			
		// Do the buffer Swap
		gl2.glFlush();
	}
}